﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using IcicleFramework.Components;
using IcicleFramework.Entities;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

namespace IcicleFramework.GameServices
{
    public class GameObjectFactory : GameService, IGameObjectFactory
    {
        public event GameObjectCreatedHandler OnGameObjectCreated;

        protected Pool<GameObject> pool;

        protected ContentManager content;

        protected XDocument xmlDoc;

        protected string pathToXML;

        protected string fullXMLPath;

        protected IComponentFactory ComponentFactory { get; set; }
        
        public string PathToXML
        {
            get { return pathToXML; }
            set
            {
                pathToXML = value;

                fullXMLPath = Path.Combine(content.RootDirectory, pathToXML);
                xmlDoc = XDocument.Load(fullXMLPath);
            }
        }

        public GameObjectFactory(ContentManager content)
        {
            this.content = content;
        }

        public override void Initialize()
        {
            this.ComponentFactory = (IComponentFactory) GameServiceManager.GetService(typeof (IComponentFactory));
            this.pool = new Pool<GameObject>(128, go => !go.Destroyed);
            pool.Deinitialize = DeinitializeGameObject;
            pool.Initialize = o => o.Reset();
        }

        protected virtual void FireOnGameObjectCreated(IGameObject newGameObject)
        {
            if (OnGameObjectCreated != null)
                OnGameObjectCreated(newGameObject);
        }

        protected void DeinitializeGameObject(GameObject gameObject)
        {
            gameObject.Active = false;
            gameObject.Dispose();
        }

        public void GenerateComponents(IGameObject gameObject, XElement element)
        {
            if (element.Element("components") != null && ComponentFactory != null)
            {
                List<XElement> componentElements = element.Element("components").Elements("component").ToList();

                //Run through all the components defined in the XElement and create them.
                for (int i = 0; i < componentElements.Count; i++)
                {
                    //Instantiate the component itself.
                    IBaseComponent component = ComponentFactory.GenerateComponent(componentElements[i], gameObject);

                    //Finally, add the component to the provided IGameObject.
                    if (component != null && component.BaseType != null)
                        gameObject.AddComponent(component.BaseType, component);
                }
            }
        }

        protected IGameObject GenerateBaseGameObject()
        {
            var gameObject = pool.New();

            //Subscribe to the OnInitialize event for this game object so we know when it's been "created" and ready to go.
            gameObject.OnInitialize += OnGameObjectInitialize;

            return gameObject;
        }

        private void OnGameObjectInitialize(IGameObject sender)
        {
            //Unsubscribe from the event...
            sender.OnInitialize -= OnGameObjectInitialize;

            //Fire our own event!
            FireOnGameObjectCreated(sender);
        }

        public IGameObject CopyExistingGameObject(IGameObject toCopy)
        {
            var gameObject = GenerateBaseGameObject();
            gameObject.Active = false;
            gameObject.DeepCopy(toCopy);

            return gameObject;
        }
        
        public IGameObject GenerateGameObjectFromName(string objectName)
        {
            IGameObject gameObject = null;
            
            if (xmlDoc != null)
            {
                gameObject = GenerateBaseGameObject();

                //Find the element corresponding to the object name, if it exists...
                if (xmlDoc.Root != null)
                {
                    List<XElement> elementList = (xmlDoc.Root.Elements("entity")
                        .Where(el => (string) el.Attribute("name") == objectName)).ToList();

                    //Just grab the first element if there was one.
                    if (elementList.Count > 0)
                    {
                        gameObject.Deserialize(elementList[0]);
                        GenerateComponents(gameObject, elementList[0]);
                    }
                }
            }

            if (gameObject != null)
            {
                gameObject.Position = new Vector2(float.NegativeInfinity, float.NegativeInfinity);
                gameObject.HasMoved = false;

                //Now that the object and all of its components have been created, and the subsystems have been notified,
                //run the 'burn in' phase by 'Initializing' them.
                gameObject.Initialize();
                gameObject.Active = true;
            }

            return gameObject;
        }

        public IGameObject GenerateGameObjectFromName(string objectName, Vector2 position)
        {
            IGameObject gameObject = null;

            if (xmlDoc != null)
            {
                gameObject = GenerateBaseGameObject();
                gameObject.Position = position;

                //Find the element corresponding to the object name, if it exists...
                if (xmlDoc.Root != null)
                {
                    List<XElement> elementList = (xmlDoc.Root.Elements("entity")
                        .Where(el => (string)el.Attribute("name") == objectName)).ToList();

                    //Just grab the first element if there was one.
                    if (elementList.Count > 0)
                    {
                        gameObject.Deserialize(elementList[0]);
                        GenerateComponents(gameObject, elementList[0]);
                    }
                }
            }

            if (gameObject != null)
            {
                gameObject.HasMoved = false;

                //Now that the object and all of its components have been created, and the subsystems have been notified,
                //run the 'burn in' phase by 'Initializing' them.
                gameObject.Initialize();

                gameObject.Active = true;
            }

            return gameObject;
        }

        public override void Update(GameTime gameTime)
        {
            pool.CleanUp();
            base.Update(gameTime);
        }
    }
}
